using System;
using System.Collections.Generic;
using UnityEngine;

public interface IEvent { } 
public static class EventBus
{
    private class Subscription
    {
        public Delegate Callback;
        public int Priority;
        public bool Once;
    }

    private static readonly Dictionary<Type, List<Subscription>> _eventTable =
        new Dictionary<Type, List<Subscription>>();
    private static readonly Dictionary<Type, IEvent> _stickyEvents = new Dictionary<Type, IEvent>();

    public static bool EnableDebug = false;

    // ==== Subscribe ====
    public static EventHandle Subscribe<T>(Action<T> callback, int priority = 0, bool once = false) where T : IEvent
    {
        var type = typeof(T);
        if (!_eventTable.TryGetValue(type, out var list))
        {
            list = new List<Subscription>();
            _eventTable[type] = list;
        }

        var sub = new Subscription { Callback = callback, Priority = priority, Once = once };
        list.Add(sub);
        list.Sort((a, b) => b.Priority.CompareTo(a.Priority));

        if (_stickyEvents.TryGetValue(type, out var data))
            callback.Invoke((T)data);

        return new EventHandle(type, callback);
    }

    // ==== Unsubscribe ====
    public static void Unsubscribe<T>(Action<T> callback) where T : IEvent
    {
        var type = typeof(T);
        if (!_eventTable.TryGetValue(type, out var list)) return;
        list.RemoveAll(s => s.Callback.Equals(callback));
    }

    // ==== Publish ====
    public static void Publish<T>(T evt) where T : IEvent
    {
        PublishInternal(evt, false);
    }

    public static void PublishSticky<T>(T evt) where T : IEvent
    {
        PublishInternal(evt, true);
    }

    private static void PublishInternal<T>(T evt, bool sticky) where T : IEvent
    {
        var type = typeof(T);
        if (sticky) _stickyEvents[type] = evt;

        if (!_eventTable.TryGetValue(type, out var list)) return;

        var copy = new List<Subscription>(list);
        foreach (var sub in copy)
        {
            if (sub.Callback is Action<T> cb) cb(evt);
            if (sub.Once) list.Remove(sub);
        }
    }

    public static void ClearAll()
    {
        _eventTable.Clear();
        _stickyEvents.Clear();
    }
}

// ========== Auto Unbind Support ==========
public class EventHandle
{
    private Type _type;
    private Delegate _callback;

    public EventHandle(Type type, Delegate callback)
    {
        _type = type;
        _callback = callback;
    }

    public void Unsubscribe()
    {
        var method = typeof(EventBus).GetMethod(nameof(EventBus.Unsubscribe))?
            .MakeGenericMethod(_type);
        method?.Invoke(null, new object[] { _callback });
    }
}

public static class EventHandleExtensions
{
    public static void AddTo(this EventHandle handle, Component owner)
    {
        owner.gameObject.AddComponent<EventUnsubscriber>().Register(handle);
    }
}

